/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved.
 *
 */

import { hvigorCore } from '@ohos/hvigor';

import { findTargetProduct } from '../../common/find-target-product.js';
import { BuildModeConst, BuildOptionConst } from '../../const/build-mode-const.js';
import { CommonConst } from '../../const/common-const.js';
import { ModuleModel } from '../../model/module/module-model.js';
import { ProjectModel } from '../../model/project/project-model.js';
import { BuildOpt, BuildOptionWithName } from '../../options/build/build-opt.js';
import { ModuleBuildProfile } from '../../options/build/module-build-profile.js';
import { ProjectBuildProfile } from '../../options/build/project-build-profile.js';
import { TaskNames } from '../../tasks/common/task-names.js';
import { getElementFromArr } from '../../utils/array-util.js';
import { OhosLogger } from '../../utils/log/ohos-logger.js';

import { BuildOptionCombiner as combiner } from './build-option-util.js';
import { BuildOptionUtil } from './build-option-util.js';
import BuildMode = ProjectBuildProfile.BuildMode;
import BuildModeBinder = ModuleBuildProfile.BuildModeBinder;
import ModuleBuildOpt = ModuleBuildProfile.ModuleBuildOpt;
import ProjectProfileOpt = ProjectBuildProfile.ProjectProfileOpt;
import { dirname, resolve, isAbsolute } from 'path';

import { Attributes, buildOptionPath } from '../../common/build-option-path-info.js';
import { moduleOhosConfigManager } from '../../common/global/module-ohos-config-manager.js';
import { GlobalProjectDataService } from "../../tasks/service/global-project-data-service";

const log = OhosLogger.getLogger('BuildModeManager');

/**
 * buildOption 中这些 key 的值如果是数组则会合并而不是替换，多层嵌套的属性 key 用 . 分隔
 */
const BUILD_OPTION_MERGE_ARRAY_KEYS = Object.freeze(['resOptions.compression.filters']);

/**
 * 1. 工程级别中product和buildMode中的无name的buildOption
 * merge(opt1, opt2, "MERGE") opt2的同名处理冲突的优先级别>opt1 以key为关键字进行递归的merge合并,数组之类的集合对象不需要单独进行合并
 *
 * 2. 模块级别的buildOptionSet中的有name的buildOption => 不允许重名
 *
 * 3. 处理上一步的buildOptionSet 和 模块中历史遗留的顶层的default的buildOption
 * buildOptionSet中的同名配置优先级更高,且采用overwrite的策略
 *
 * 4. 模块级别继承工程级别合并后的buildOption
 * 取上一步合并后的buildOptionSet中的每一个buildOption与该继承过来的buildOption做一个merge，并且buildOptionSet中的buildOption的优先级更高
 *
 * 5. 根据buildMode和buildOptionBinder可以从上一步处理的set中找到一个target对应的唯一一个buildOption
 *
 * 6. target中的配置的无name的buildOption与上一步中得到的buildOption
 * target中的buildOption优先级更高，且采用merge的策略与上一步的buildOption合并
 *
 * 7. 模块级hvigorfile.ts的配置值
 *
 * 8. 命令行处理
 */
export class BuildModeManager {
  private productBuildOptionMap: Map<string, BuildOpt> = new Map();
  // 结构为 <moduleName, <targetName, BuildOpt>>
  private targetBuildOptionMap: Record<string, Map<string, BuildOpt>> = {};
  // 结构为 <productName, <buildMode, targetBuildOptionMap>>
  private targetBuildOptionMapWithProductBuildMode: Record<string, Record<string, Record<string, Map<string, BuildOpt>>>> = {};

  public initProjectBuildOptions(projectModel: ProjectModel, orginPath: string) {
    this.productBuildOptionMap.clear();
    const buildMode = BuildModeManager.getBuildMode();
    let productBuildOptionPathMap: Map<string, Attributes> = new Map();

    // 延迟报错，如果在扩展插件的生命周期中新增了buildMode的情况下，会重新触发这个init，如果在最后一次能拿到插件中新增的buildMode则不报错
    if(BuildOptionUtil.validateBuildMode(buildMode, projectModel)) {
      GlobalProjectDataService.getInstance().setBuildModeInfo({
        ifThrowError: true,
        buildModeName: buildMode,
        buildProfilePath: projectModel.getProfilePath(),
      })
    } else {
      GlobalProjectDataService.getInstance().setBuildModeInfo({
        ifThrowError: false,
        buildModeName: buildMode,
        buildProfilePath: projectModel.getProfilePath(),
      })
    }
    const cliBuildOpts = {
      debuggable: hvigorCore.getExtraConfig().get(CommonConst.DEBUGGABLE),
    };
    log.debug(`Start initialize project's product build option map with build mode ${buildMode}.`);

    const projectBuildProfile = projectModel.getProfileOpt();

    // 1. 补充BuildModeSet内的默认配置
    const projectProducts = projectBuildProfile.app.products!;
    let projectBuildModes: BuildMode[] = projectBuildProfile.app.buildModeSet ?? [];
    projectBuildModes = BuildOptionUtil.arrayDeduplication(projectBuildModes);
    const buildModeBuildOpt = getElementFromArr(projectBuildModes, buildMode)?.buildOption ?? {};
    projectProducts.forEach((product) => {
      let productResultBuildOpt: BuildOpt = {};

      // 1. 命令行参数覆盖
      if (cliBuildOpts.debuggable) {
        buildModeBuildOpt.debuggable = hvigorCore.getExtraConfig().get(CommonConst.DEBUGGABLE) === 'true';
      }

      // 2. 合并当前product内配置的buildOption
      const productBuildOpt = product.buildOption ?? {};
      log.debug(`Picking option from product '${product.name}' with build mode '${buildMode}'.`);
      productResultBuildOpt = combiner.mergeBuildOption(productBuildOpt, productResultBuildOpt, BUILD_OPTION_MERGE_ARRAY_KEYS);
      productBuildOptionPathMap = combiner.mergeBuildOptionPath(productResultBuildOpt, orginPath, productBuildOptionPathMap);

      // 3. 合并当前buildMode内配置的buildOption
      productResultBuildOpt = combiner.mergeBuildOption(buildModeBuildOpt, productResultBuildOpt, BUILD_OPTION_MERGE_ARRAY_KEYS);
      productBuildOptionPathMap = combiner.mergeBuildOptionPath(productResultBuildOpt, orginPath, productBuildOptionPathMap);

      // 4. 合并buildModeSet中的隐式配置
      const defaultBuildModeBuildOpt = BuildOptionUtil.getDefaultBuildModeBuildOpt(buildMode);
      productResultBuildOpt = combiner.mergeBuildOption(defaultBuildModeBuildOpt, productResultBuildOpt, BUILD_OPTION_MERGE_ARRAY_KEYS);
      productBuildOptionPathMap = combiner.mergeBuildOptionPath(productResultBuildOpt, orginPath, productBuildOptionPathMap);

      // 5. 将超纹理配置项中的 path 从相对路径转为绝对路径
      const buildProfileDir = dirname(projectModel.getProfilePath());
      this.convertCompressionPathToAbsolute(productResultBuildOpt, buildProfileDir);

      log.debug(`Product '${product.name}' build option: ${JSON.stringify(buildModeBuildOpt, null, '  ')}`);
      buildOptionPath.setProductOptPath(productBuildOptionPathMap, product.name);
      this.productBuildOptionMap.set(product.name, productResultBuildOpt);
    });

    log.debug(`End initialize project's product build option map with build mode '${buildMode}'.`);
  }

  /**
   * 初始化模块级的buildOption
   * @param moduleModel 模块模型
   * @param path 模块路径
   */
  public initModuleBuildOptions(moduleModel: ModuleModel, path: string) {
    // 初始化各（product，buildMode）下对应的buildOptionMap
    this.initModuleBuildOptionMapWithProductBuildMode(moduleModel, path);
    // 初始化全局的buildOptionMap
    this.initModuleBuildOptionMap(moduleModel, path, this.targetBuildOptionMap);
  }

  /**
   * 根据BuildProfile的配置，将环境中所有可能存在的product，buildMode组合配对，计算对应的buildOption并存入buildOptionMapWithProductBuildMode
   * @param moduleModel 模块模型
   * @param path 模块路径
   */
  private initModuleBuildOptionMapWithProductBuildMode(moduleModel: ModuleModel, path: string) {
    // 提前存储config的product和buildMode以便在修改config之后复原
    const configBuildMode = hvigorCore.getExtraConfig().get('buildMode');
    const configProduct = hvigorCore.getExtraConfig().get('product');
    const rootProject = moduleModel.getParentProject();
    // 获取工程级buildProfile的所有的构建模式
    const allBuildModes = this.getAllBuildModes(rootProject);
    // 处理项目buildProfile不含RELEASE，DEBUG的情况
    for (let buildMode of [BuildModeConst.RELEASE, BuildModeConst.DEBUG]) {
      if (!allBuildModes.includes(buildMode)) {
        allBuildModes.push(buildMode);
      }
    }
    const allProducts = rootProject.getProductNames();
    // 遍历所有product信息
    for (let curProduct of allProducts) {
      if (!this.targetBuildOptionMapWithProductBuildMode[curProduct]) {
        this.targetBuildOptionMapWithProductBuildMode[curProduct] = {};
      }
      const targetBuildOptionMapWithBuildMode = this.targetBuildOptionMapWithProductBuildMode[curProduct];
      // 遍历所有buildModes
      for (let curBuildMode of allBuildModes) {
        if (!targetBuildOptionMapWithBuildMode[curBuildMode]) {
          targetBuildOptionMapWithBuildMode[curBuildMode] = {};
        }
        const extraConfig = hvigorCore.getExtraConfig();
        extraConfig.set('product', curProduct);
        extraConfig.set('buildMode', curBuildMode);
        hvigorCore.setExtraConfig(extraConfig);
        // 根据当前的product和buildMode，设置buildOption
        this.initModuleBuildOptionMap(moduleModel, path, targetBuildOptionMapWithBuildMode[curBuildMode]);
      }
    }
    // 复原config配置项
    const extraConfig = hvigorCore.getExtraConfig();
    configProduct ? extraConfig.set('product', configProduct) : extraConfig.delete('product');
    configBuildMode ? extraConfig.set('buildMode', configBuildMode) : extraConfig.delete('buildMode');
    hvigorCore.setExtraConfig(extraConfig);
  }

  /**
   * 根据当前的环境的product，buildMode，获取指定module每个target的buildOptions并将结果赋给buildOptionMap
   * @param moduleModel 需要获取buildOption的模块
   * @param path 路径
   * @param buildOptionMap 获取到buildOptions后将赋值给此对象
   */
  // 新建变量对象太多，考虑减少新建变量
  private initModuleBuildOptionMap(moduleModel: ModuleModel, path: string, buildOptionMap: Record<string, Map<string, BuildOpt>>) {
    const moduleName = moduleModel.getName();
    const buildMode = BuildModeManager.getBuildMode();
    buildOptionMap[moduleName] ? buildOptionMap[moduleName].clear() : (buildOptionMap[moduleName] = new Map());
    const projectBuildProfile: ProjectProfileOpt = moduleModel.getParentProject().getProfileOpt();
    const projectBuildModes: BuildMode[] = BuildOptionUtil.arrayDeduplication(projectBuildProfile.app.buildModeSet ?? []);
    const moduleBuildProfile: ModuleBuildOpt = moduleModel.getProfileOpt();
    const moduleBinderSet: BuildModeBinder[] = BuildOptionUtil.getOrDefaultBuildModeBinderSet(moduleBuildProfile.buildModeBinder ?? []);
    let moduleBuildOptSet: BuildOptionWithName[] = moduleBuildProfile.buildOptionSet ?? [];

    // 延迟报错，如果在扩展插件的生命周期中新增了buildMode的情况下，会重新触发这个init，如果在最后一次能拿到插件中新增的buildMode则不报错
    if(BuildOptionUtil.validateBuildMode(buildMode, moduleModel.getParentProject())) {
      GlobalProjectDataService.getInstance().setBuildModeInfo({
        ifThrowError: true,
        buildModeName: buildMode,
        buildProfilePath: moduleModel.getParentProject().getProfilePath(),
      })
    } else {
      GlobalProjectDataService.getInstance().setBuildModeInfo({
        ifThrowError: false,
        buildModeName: buildMode,
        buildProfilePath: moduleModel.getParentProject().getProfilePath(),
      })
    }

    log.debug(`Start initialize module-target build option map, moduleName=${moduleName}, buildMode=${buildMode}`);

    // 删除 buildOptionSet 中的同名配置， 以书写顺序后面的整体替换前面的同名buildOption；
    moduleBuildOptSet = BuildOptionUtil.arrayDeduplication(moduleBuildOptSet);

    // 处理buildOptionSet中的 copyFrom 合并逻辑
    combiner.processCopyFrom(moduleBuildOptSet);

    moduleModel.getTargetOptions().forEach((target) => {
      let targetResultBuildOpt: BuildOptionWithName = {};
      let targetResultBuildOptPath: Map<string, Attributes> = new Map();
      const product = findTargetProduct(moduleModel.getParentProject());

      // 1. 命令行参数覆盖
      if (hvigorCore.getExtraConfig().get(CommonConst.DEBUGGABLE)) {
        targetResultBuildOpt.debuggable = hvigorCore.getExtraConfig().get(CommonConst.DEBUGGABLE) === 'true';
      }

      // 1.5. hvigorfile参数覆盖

      const hvigorfileConfigBuildOpt: BuildOpt | undefined = moduleOhosConfigManager.getBuildOption(moduleName);
      targetResultBuildOpt = combiner.mergeBuildOption(hvigorfileConfigBuildOpt, targetResultBuildOpt, BUILD_OPTION_MERGE_ARRAY_KEYS);
      targetResultBuildOptPath = combiner.mergeBuildOptionPath(
        targetResultBuildOpt,
        `${moduleModel.getModule().getBuildFilePath()}.ts`,
        targetResultBuildOptPath,
      );

      // 2. 合并target config中的配置的buildOption
      const targetConfigBuildOpt = target.config?.buildOption ?? {};
      log.debug(`Target '${target.name}' config: ${JSON.stringify(targetConfigBuildOpt, null, '  ')}`);
      targetResultBuildOpt = combiner.mergeBuildOption(targetConfigBuildOpt, targetResultBuildOpt, BUILD_OPTION_MERGE_ARRAY_KEYS);
      targetResultBuildOptPath = combiner.mergeBuildOptionPath(targetResultBuildOpt, path, targetResultBuildOptPath);

      // 3. 根据targetName，通过buildModeBinder，从buildOptionSet中找出来一个对应的buildOption合并
      const targetBuildOptionName = this.getBuildOptNameFromMapping(moduleBinderSet, buildMode, target.name);
      const targetBuildOption = getElementFromArr(moduleBuildOptSet, targetBuildOptionName) ?? {};
      targetResultBuildOpt = combiner.mergeBuildOption(targetBuildOption, targetResultBuildOpt, BUILD_OPTION_MERGE_ARRAY_KEYS);
      targetResultBuildOptPath = combiner.mergeBuildOptionPath(targetResultBuildOpt, path, targetResultBuildOptPath);

      // 4. 合并moduleBaseBuildOpt
      targetResultBuildOpt = combiner.mergeBuildOption(moduleBuildProfile.buildOption ?? {}, targetResultBuildOpt, BUILD_OPTION_MERGE_ARRAY_KEYS);
      targetResultBuildOptPath = combiner.mergeBuildOptionPath(targetResultBuildOpt, path, targetResultBuildOptPath);

      // 4.5 经过前面的步骤后，targetResultBuildOpt.resOptions.compression.filters[].files.path 都是模块级的相对路径，需要转换为绝对路径
      const buildProfileDir = dirname(moduleModel.getProfilePath());
      this.convertCompressionPathToAbsolute(targetResultBuildOpt, buildProfileDir);

      // 5. 合并product中的buildOption，由于 product.buildOption 在工程级 build-profile 中，需要将其中的超纹理配置 path 先转成绝对路径
      const productBuildOpt = product.buildOption ?? {};
      targetResultBuildOpt = combiner.mergeBuildOption(productBuildOpt, targetResultBuildOpt, BUILD_OPTION_MERGE_ARRAY_KEYS);
      const productOptPath = buildOptionPath.getProductOptPath(product.name);
      targetResultBuildOptPath = combiner.mergeProductBuildOptionPath(productOptPath ?? new Map(), targetResultBuildOptPath);

      // 6. 合并buildMode中的buildOption，由于 buildMode.buildOption 在工程级 build-profile 中，需要将其中的超纹理配置 path 先转成绝对路径
      const buildModeBuildOpt = getElementFromArr(projectBuildModes, buildMode)?.buildOption ?? {};
      targetResultBuildOpt = combiner.mergeBuildOption(buildModeBuildOpt, targetResultBuildOpt, BUILD_OPTION_MERGE_ARRAY_KEYS);
      targetResultBuildOptPath = combiner.mergeBuildOptionPath(targetResultBuildOpt, path, targetResultBuildOptPath);

      // 6.5 经过第5、6步后，targetResultBuildOpt.resOptions.compression.filters 新增了工程级 filter，其中的 path 都是工程级的相对路径，需要转换为绝对路径
      this.convertCompressionPathToAbsolute(targetResultBuildOpt, moduleModel.getBelongProjectPath());

      // 7. 合并buildOptionSet中的隐式配置
      const defaultTargetBuildOpt = BuildOptionUtil.getDefaultModuleBuildOpt(targetBuildOptionName);
      targetResultBuildOpt = combiner.mergeBuildOption(defaultTargetBuildOpt, targetResultBuildOpt, BUILD_OPTION_MERGE_ARRAY_KEYS);

      // 8. 合并buildModeSet中的隐式配置
      const defaultBuildModeBuildOpt = BuildOptionUtil.getDefaultBuildModeBuildOpt(buildMode);
      targetResultBuildOpt = combiner.mergeBuildOption(defaultBuildModeBuildOpt, targetResultBuildOpt, BUILD_OPTION_MERGE_ARRAY_KEYS);
      buildOptionPath.setBuildOptionPath(moduleName, target.name, targetResultBuildOptPath);

      buildOptionMap[moduleName].set(target.name, targetResultBuildOpt);
    });

    buildOptionMap[moduleName]?.forEach((val, key) => {
      log.debug(`Module '${moduleName}' target '${key}' build option: ${JSON.stringify(val, null, '  ')}`);
    });
    log.debug(`End initialize module-target build option map, moduleName=${moduleName}`);
  }

  private getBuildOptNameFromMapping(binderSet: BuildModeBinder[], buildMode: string, target: string): string {
    // 5. 根据buildMode name找到对应的buildModeBinder，以此获取当前buildMode下，targetName与buildOption的mapping
    const buildModeBinder =
      binderSet.find((binder) => {
        if (binder.buildModeName === buildMode) {
          return binder;
        }
        return undefined;
      }) ?? ({} as BuildModeBinder);
    const buildModeMappings = buildModeBinder.mappings ?? [];
    for (const mapping of buildModeMappings) {
      if (mapping.targetName === target) {
        return mapping.buildOptionName;
      }
    }

    /*
     * 6. 根据buildMode, 在选择的target没有声明与buildOption的映射关系时，有默认的buildOption返回值
     * - debug mode：优先分配 debug buildOption，测试包（ohosTest）分配 default buildOption
     * - release mode：优先分配 release buildOption，测试包（ohosTest）分配 default buildOption
     * - test mode：优先分配 debug buildOption，测试包（ohosTest）分配 default buildOption，
     * - 其他mode: 默认分配default buildOption
     */
    log.debug(`Does not get target '${target}'corresponding build option.`);
    switch (buildMode) {
      case BuildModeConst.DEBUG:
      case BuildModeConst.TEST:
        log.debug("Using preset build option 'debug'.");
        return BuildOptionConst.DEBUG;
      case BuildModeConst.RELEASE:
        log.debug("Using preset build option 'release'.");
        return BuildOptionConst.RELEASE;
      default:
        log.debug("Using preset build option 'default'.");
        return BuildOptionConst.DEFAULT;
    }
  }

  /**
   * 获取对应product在该次构建过程中使用的buildOption
   * @param {string} product
   * @returns {BuildOpt}
   */
  public getProductBuildOption(product: string): BuildOpt {
    const productBuildOption = this.productBuildOptionMap.get(product) ?? {};
    log.debug(`Product '${product}' using build option: ${JSON.stringify(productBuildOption, null, '  ')} in this build.`);
    return productBuildOption;
  }

  /**
   * 获取某模块对应target在该次构建过程中使用的buildOption
   * @param {string} moduleName
   * @param {string} target
   * @returns {BuildOpt}
   */
  public getTargetBuildOption(moduleName: string, target: string): BuildOpt {
    const targetBuildOption = this.targetBuildOptionMap[moduleName]?.get(target) ?? {};
    log.debug(`Module '${moduleName}' target '${target}' using build option: ${JSON.stringify(targetBuildOption, null, '  ')} in this build.`);
    return targetBuildOption;
  }

  /**
   * 获取某模块对应target在对应product，对应buildMode构建过程中使用的buildOption
   * @param {string} moduleName
   * @param {string} target
   * @param {string} product
   * @param {string} buildMode
   * @returns {BuildOpt}
   */
  public getTargetBuildOptionWithProductBuildMode(moduleName: string, target: string, buildMode: string, product: string = 'default'): BuildOpt {
    const targetBuildOption = this.targetBuildOptionMapWithProductBuildMode[product][buildMode][moduleName]?.get(target) ?? {};
    log.debug(`Module '${moduleName}' target '${target}' using build option: ${JSON.stringify(targetBuildOption, null, '  ')} in ${buildMode} buildMode.`);
    return targetBuildOption;
  }

  static getBuildMode(): string {
    const cliBuildMode = hvigorCore.getExtraConfig().get('buildMode');
    if (cliBuildMode) {
      return cliBuildMode;
    }

    // 兼容性处理，命令行中没有buildMode时，assembleApp任务会将buildMode强制设置成release
    if (hvigorCore.isCommandEntryTask(TaskNames.CommonHookTask.ASSEMBLE_APP.name)) {
      return BuildModeConst.RELEASE;
    }
    return BuildModeConst.DEBUG;
  }

  /**
   * 获取项目buildProfile声明的所有构建模式
   * @param {ProjectModel} project - 项目模型
   * @return {string[]} buildModes - 返回一个包含所有构建模式名称的数组
   */
  private getAllBuildModes(project: ProjectModel): string[] {
    const buildProfile = project.getProfileOpt();
    const buildModes: string[] = [];
    buildProfile?.app?.buildModeSet?.forEach((buildMode: any) => {
      buildModes.push(buildMode?.name);
    });
    return buildModes;
  }

  /**
   * 将 buildOption 中的超纹理配置项中的 path 从相对路径转换为绝对路径
   * 由于无法区分 path 是相对于工程还是模块，所以需要刚读取完模块级的就马上进行转换，然后再去读取工程级的，假如 path 是绝对路径视为已经转换过了
   * 只能用于转换合并后的配置项结果对象 productResultBuildOpt 和 targetResultBuildOpt，不可用于修改原配置项对象，用户需要在 hvigorfile.ts 中读取到原本的 build profile
   * @param buildOption
   * @param cwd
   * @private
   */
  private convertCompressionPathToAbsolute(buildOption: BuildOpt, cwd: string) {
    buildOption.resOptions?.compression?.filters?.forEach((filter) => {
      if (filter.files?.path) {
        filter.files.path = this.convertPathToAbsolute(filter.files.path, cwd);
      }
      if (filter.exclude?.path) {
        filter.exclude.path = this.convertPathToAbsolute(filter.exclude.path, cwd);
      }
    });
  }

  /**
   * 用户填的超纹理 path 只会是相对路径，但在将工程级合并到模块级时，工程级配置里的 path 会是转换后的绝对路径则不需要再拼接
   * @param pathArr
   * @param cwd
   * @private
   */
  private convertPathToAbsolute(pathArr: string[], cwd: string) {
    return pathArr.map((inputGlobPath) => {
      // 只对用户填写的相对路径做转换，绝对路径认为是已经转换过的
      if (isAbsolute(inputGlobPath)) {
        return inputGlobPath;
      }
      return resolve(cwd, inputGlobPath);
    });
  }
}

export const buildOptionManager = new BuildModeManager();
